package code.easy.paging

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn


/**
 * 加载网络数据源或者自定义的数据源
 *
 * @param clearWhenRefreshFailed 当刷新失败时是否清除当前列表中的数据
 */
open class EasyPagingSource<T : Any>(
    viewModelScope: CoroutineScope,
    pageSize: Int = 20,
    initialLoadSize: Int = 20,
    prefetchDistance: Int = 3,
    enablePlaceholders: Boolean = false,
    val startPageIndex: Int = DefaultStartPageIndex,
    var clearWhenRefreshFailed: Boolean = false,
    private val dataSource: suspend (pageIndex: Int) -> MutableList<T>?
) {
    companion object {
        /** 全局设置默认初始加载页Index */
        var DefaultStartPageIndex = 1
    }

    val data = mutableListOf<T>()

    private var isUpdate = false
    private var nextKey: Int? = null
    private lateinit var innerPagingSource: InnerPagingSource

    private val pager = Pager(
        PagingConfig(
            pageSize = pageSize, initialLoadSize = initialLoadSize,
            prefetchDistance = prefetchDistance,
            enablePlaceholders = enablePlaceholders
        )
    ) {
        innerPagingSource = InnerPagingSource()
        innerPagingSource
    }

    val flow = pager.flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)

    /**
     * 通过更新数据源来实现Compose中的列表UI刷新
     * Compose中由于列表没有传统RecyclerView notify通知刷新的功能，只能通过刷新数据源来通知UI刷新
     */
    open fun notifyDataSetChange() {
        isUpdate = true
        innerPagingSource.invalidate()
    }

    /**
     * 默认当 dataSource() 返回列表为空时，pager自动标记为没有更多数据。
     * 若想自己定义加载处理逻辑，可以重写此方法。具体用法可以查看PagingSource官方文档
     */
    open suspend fun onLoad(pageIndex: Int, params: PagingSource.LoadParams<Int>): PagingSource.LoadResult<Int, T> {
        val result: MutableList<T>? = dataSource.invoke(pageIndex)
        if (params is PagingSource.LoadParams.Refresh) data.clear()
        result?.let { data.addAll(it) }
        nextKey = if (result.isNullOrEmpty()) null else pageIndex + 1
        return PagingSource.LoadResult.Page(
            data = if (result.isNullOrEmpty()) mutableListOf() else result,
            prevKey = if (pageIndex < startPageIndex) null else pageIndex - 1,
            nextKey = nextKey
        )

    }

    private inner class InnerPagingSource : PagingSource<Int, T>() {

        override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
            if (params is LoadParams.Refresh && isUpdate) {
                isUpdate = false
                return LoadResult.Page(data = data, prevKey = null, nextKey = nextKey)
            } else if (params is LoadParams.Append) {
                isUpdate = false
            }
            val pageIndex = params.key ?: startPageIndex
            try {
                return onLoad(pageIndex, params)
            } catch (exception: Exception) {
                if (params is LoadParams.Refresh) {
                    return if (clearWhenRefreshFailed) {
                        data.clear()
                        LoadResult.Error(exception)
                    } else {
                        LoadResult.Page(
                            data = data,
                            prevKey = null,
                            nextKey = nextKey
                        )
                    }
                }
                return LoadResult.Error(exception)
            }
        }

        override fun getRefreshKey(state: PagingState<Int, T>): Int? {
            return null
        }
    }
}